﻿using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using TeamTasker.Contracts;
using TeamTasker.Utils;
using TeamTasker.Server.BusinessLogic;
using TeamTasker.Server.BusinessLogic.Staff;
using Spring.Data.Common;
using Spring.Data.Core;
using System.Configuration;
using System.Collections.Specialized;

namespace TeamTasker.Server.Service
{
    // NOTE: If you change the class name "TeamTaskerService" here, you must also update the reference to "TeamTaskerService" in Web.config and in the associated .svc file.
    public class TeamTaskerService : ITeamTaskerService
    {
        // Handling WCF service start event via static constructor:
        // http://stackoverflow.com/questions/739268/wcf-application-start-event
        static TeamTaskerService()
        {
            ApplicationInitializer appInit = new ApplicationInitializer();

            appInit.LogConfigPath = GetConfigParam("log.config");
            appInit.LogOutputPath = GetConfigParam("log.outDir");
            appInit.LogLevel = GetConfigParam("log.level");
            appInit.LogsStorageSize = int.Parse(GetConfigParam("log.storageSize"));
            appInit.BeanContainerConfigPath = GetConfigParam("beans.config");

            appInit.InitializeApplication();
            CheckInitializationState();
            InitializeStaffRepoConnector();
            Log.Debug("TeamTasker static constructor is called");
        }

        public TeamTaskerService()
        {
            Log.Debug("TeamTasker non-static constructor is called");
        }

        protected static string GetConfigParam(string paramName)
        {
            string param = ConfigurationSettings.AppSettings[paramName];
            return HandleMacros(param);
        }

        protected static string HandleMacros(string paramValue)
        {
            if (paramValue != null && paramValue.Contains("${"))
            {
                int idxStart = paramValue.IndexOf("${") + 2;
                int idxEnd = paramValue.IndexOf("}", idxStart);
                string macro = paramValue.Substring(idxStart, idxEnd - idxStart);
                string value = null;
                if (macro == "CurDir")
                {
                    value = Environment.CurrentDirectory;
                }
                else
                {
                    value = ConfigurationSettings.AppSettings[macro];
                }
                string newParamValue = paramValue.Replace("${" + macro + "}", value);
                paramValue = HandleMacros(newParamValue);
            }
            return paramValue;
        }

        // TODO: think how to remove dependency on components from this class but leave ability 
        // to configure them in application config instead of Spring. 
        protected static void InitializeStaffRepoConnector()
        {
            string staffRepoType = GetConfigParam("staff.repository");
            IStaffRepositoryConnector connector = null;
            if ("file" == staffRepoType)
            {
                PlainFileStaffRepositoryConnector conn =
                    BeanFactory.Instance.GetObject("fileStaffRepoConnector") as PlainFileStaffRepositoryConnector;
                conn.FilePath = GetConfigParam("staff.filePath");
                connector = conn;
            }
            else if ("ldap" == staffRepoType)
            {
                LdapStaffRepositoryConnector conn =
                    BeanFactory.Instance.GetObject("ldapStaffRepoConnector") as LdapStaffRepositoryConnector;
                conn.Url = GetConfigParam("staff.ldapUrl");
                conn.Login = GetConfigParam("staff.ldapUser");
                conn.Password = GetConfigParam("staff.ldapPassword");
                connector = conn;
            }
            else
            {
                throw new ConfigurationErrorsException("Unrecognized staff repo connector type: " + staffRepoType);
            }
            SessionFacade.StaffRepository.StaffRepoConnector = connector;
        }

        protected static void CheckInitializationState()
        {
            Validate.NotNull(SessionFacade, "Session facade is not configured");
            Validate.NotNull(UserFacade, "User facade is not configured");
            Validate.NotNull(TeamFacade, "Team facade is not configured");
            Validate.NotNull(TaskFacade, "Task facade is not configured");
        }

        protected static SessionFacade SessionFacade
        {
            get { return BeanFactory.Instance.GetObject("sessionFacade") as SessionFacade; }
        }

        protected static UserFacade UserFacade
        {
            get { return BeanFactory.Instance.GetObject("userFacade") as UserFacade; }
        }

        protected static TeamFacade TeamFacade
        {
            get { return BeanFactory.Instance.GetObject("teamFacade") as TeamFacade; }
        }

        protected static TaskFacade TaskFacade
        {
            get { return BeanFactory.Instance.GetObject("taskFacade") as TaskFacade; }
        }

        # region Client session management

        private IServiceSessionManager sessionManager;

        public IServiceSessionManager SessionManager
        {
            get
            {
                if (sessionManager == null)
                {
                    sessionManager = BeanFactory.Instance.GetObject("serviceSessionManager") as IServiceSessionManager;
                    Validate.NotNull(sessionManager, "Service session manager is not initialized");
                }
                return sessionManager;
            }
            set
            {
                sessionManager = value;
            }
        }

        protected ClientSession OnMethodCall(string methodName)
        {
            ClientSession session = GetSession();
            Validate.NotNull(session, "Request is not authorized: client session is NULL in service operation context");
            session.LastRequestTime = DateTime.Now;
            Log.Info("User with ID " + session.UserID + " requested an execution of method " + methodName);
            return session;
        }

        protected void CreateSession(Session session)
        {
            SessionManager.CreateSession(session);
        }

        protected ClientSession GetSession()
        {
            return SessionManager.GetSession();
        }

        #endregion

        #region ITeamTaskerService Members

        public long Login(string loginId, string password)
        {
            Session session = SessionFacade.Login(loginId, password);
            if (session != null)
            {
                CreateSession(session);
                return session.ID;
            }
            return -1;
        }

        public IList<User> GetUserList()
        {
            OnMethodCall("GetUserList");
            return UserFacade.GetUserList();
        }

        public IList<Team> GetTeamList()
        {
            ClientSession session = OnMethodCall("GetTeams");
            long userID = session.UserID;
            return TeamFacade.GetTeams(userID);
        }

        public Team GetTeam(long teamID)
        {
            OnMethodCall("GetTeam");
            return TeamFacade.Load(teamID);
        }

        public void SaveTeam(Team team)
        {
            OnMethodCall("SaveTeam");
            TeamFacade.Save(team);
        }

        public void DeleteTeam(long teamID)
        {
            OnMethodCall("DeleteTeam");
            TeamFacade.Delete(teamID);
        }

        public void UpdateTeamMembers(long teamID,
                                      IList<TeamMember> membersToAdd,
                                      IList<TeamMember> membersToUpdate,
                                      IList<TeamMember> membersToDelete)
        {
            OnMethodCall("UpdateTeamMembers");
            TeamFacade.UpdateMembers(teamID,
                membersToAdd, membersToUpdate, membersToDelete);
        }

        public IList<Task> GetTaskList(long assignedUserID, 
                                       DateTime start, DateTime end)
        {
            OnMethodCall("GetTaskList");
            return TaskFacade.GetTaskList(assignedUserID, start, end);
        }


        public void SaveTask(Task task)
        {
            ClientSession session = OnMethodCall("SaveTask");
            if (task.ID == 0)
            {
                task.CreatedBy = session.UserID;
                task.CreationDate = DateTime.Now;
            }
            else
            {
                task.ModifiedBy = session.UserID;
                task.ModificationDate = DateTime.Now;
            }
            TaskFacade.Save(task);
        }

        public Task GetTask(long taskID)
        {
            OnMethodCall("GetTask");
            return TaskFacade.Load(taskID);
        }

        public void AssignTask(long taskID, long userID)
        {
            ClientSession session = OnMethodCall("AssignTask");
            long managerID = session.UserID;
            TaskFacade.AssignTask(taskID, userID, managerID);
        }

        public void AddTaskComment(long taskID, string comment)
        {
            ClientSession session = OnMethodCall("AddTaskComment");
            long userID = session.UserID;
            TaskFacade.AddTaskComment(taskID, comment, userID);
        }

        public void ApproveAssignment(bool isApproved, long assignmentId)
        {
            throw new NotImplementedException();
        }

        public bool Assign(Task task, long userID)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<Notification> QueryMissedNotifications()
        {
            throw new NotImplementedException();
        }

        public void RegisterWorklog(TaskWorkLogEntry logEntry)
        {
            throw new NotImplementedException();
        }

        public void RequestEstimation(long userID)
        {
            throw new NotImplementedException();
        }

        /*
        public void UpdateEntity(BaseEntity newEntity, long entityId, Type entityType)
        {
            throw new NotImplementedException();
        }
        */
        #endregion
    }
}